import * as THREE from 'three'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
import AssestManager from '../base/assest'
import { GLTF } from "three/examples/jsm/loaders/GLTFLoader";
import { Object3D } from 'three';
import { createContactShadow } from "../utils/contactShadow";
const isDebug = true;

export default class World {
  private world: THREE.Scene
  private camera: THREE.PerspectiveCamera
  private renderer: THREE.WebGLRenderer
  private controls: OrbitControls
  public assest: { [key: string]: GLTF } = {};
  public name2Action: { [key: string]: any } = {};
  private clock = new THREE.Clock();
  private mixer : THREE.AnimationMixer | null = null;
  constructor() {
    this.world = new THREE.Scene()
    this.world.background = new THREE.Color(0xe4f4c0)
    this.camera = new THREE.PerspectiveCamera(30, window.innerWidth / window.innerHeight)
    this.renderer = new THREE.WebGLRenderer({ antialias: true })
    this.controls = new OrbitControls(this.camera, this.renderer.domElement)
    this.setCamera();
    this.setRenderer();
  }

  setCamera() {
    this.camera.position.set(0, 0.8, 8);
    this.camera.castShadow = true;
  }

  setRenderer() {
    this.renderer.shadowMap.type = THREE.PCFSoftShadowMap;
    this.renderer.setSize(window.innerWidth, window.innerHeight)
    this.renderer.setPixelRatio(window.devicePixelRatio)
    this.renderer.outputColorSpace = THREE.SRGBColorSpace;;
    this.renderer.shadowMap.enabled = true;
    document.body.appendChild(this.renderer.domElement)
  }

  setContactShadow () {
    const shadowGroup = new THREE.Group();
    shadowGroup.position.set(0, -1, 0);
    shadowGroup.rotation.set(0, 0, 0);
    this.world.add(shadowGroup);
    createContactShadow(this.world, this.renderer, shadowGroup);
    return this;
  }

  async addAssests(assestManager: AssestManager, onProgress: Function) {
    this.assest = await assestManager.load(onProgress);
    return this;
  }

  public addComponent(component: Object3D | GLTF) {
    if(component instanceof Object3D) {
        this.world.add(component);
        return this;
    }
    if (component.animations && component.animations.length > 0) {
        this.mixer = new THREE.AnimationMixer(component.scene)
        component.animations.forEach(element => {
            console.log(element.name)
            this.name2Action[element.name] = this.mixer!.clipAction(element);
        });
    }
    this.world.add(component.scene);
    return this;
  }

  public addLight(light: Object3D | Object3D[], helper: boolean = false) {
    if (Array.isArray(light)) {
      light.forEach((item) => {
        this.addLight(item, helper)
      })
      return this;
    }
    this.setLightHelper(light, helper);
    this.world.add(light)
    return this;
  }

  private setLightHelper(light: Object3D, helper: boolean = false) {
    if (!isDebug) return;
    if (!helper) return;
    if (light instanceof THREE.SpotLight) {
        const lightHelper = new THREE.SpotLightHelper(light, 1)
        lightHelper.visible = true
        this.world.add(lightHelper)
    }
    if (light instanceof THREE.PointLight) {
        const lightHelper = new THREE.PointLightHelper(light, 1)
        lightHelper.visible = true
        this.world.add(lightHelper)
    }
    if (light instanceof THREE.DirectionalLight) {
        const lightHelper = new THREE.DirectionalLightHelper(light, 1)
        lightHelper.visible = true
        this.world.add(lightHelper)
    }
    if (light instanceof THREE.HemisphereLight) {
        const lightHelper = new THREE.HemisphereLightHelper(light, 1)
        lightHelper.visible = true
        this.world.add(lightHelper)
    }
  }

  private preAnimation = '';
  public setAnimation(name: string) {
    const mixer = this.name2Action[name];
    const preMixer = this.name2Action[this.preAnimation];
    if (mixer) {
      mixer.reset();
      mixer.play();
      this.preAnimation = name;
      if (preMixer) {
        mixer.crossFadeFrom(preMixer, 0.5, false)
      }
    }
    return this;
  }

  public start(): void {
    requestAnimationFrame(this.start.bind(this))
    this.controls.update()
    if (this.mixer) this.mixer.update(this.clock.getDelta())
    this.renderer.render(this.world, this.camera)
  }
}
